package org.hawk.gwt.ppc.scanner.utils;

/**
 * Provides tokenizer implementation for java language.
 * Tokenization is based on wherever token is java indentifier or not.
 * 
 * @see Character#isJavaIdentifierStart(char)
 * @see Character#isJavaIdentifierPart(char)
 * @author alex.bereznevatiy@gmail.com
 *
 */
public class JavaTokenizer {
	
	/*
	 * Helper interface to abstract from input source.
	 */
	private static interface CharIterator {
		char next();
		
		boolean hasNext();
		
		boolean isJavaStart();
		
		boolean isJavaPart();
		
		boolean isWhiteSpace();
	}
	
	private CharIterator iterator;
	private String current;
	
	/**
	 * Constructs tokenizer for string presentation of some part of java source.
	 * @param src
	 */
	public JavaTokenizer(final String source) {
		final String src = source.trim();
		iterator = new CharIterator() {
			
			private int index = 1;
			private final int length = src.length();
			private char ch = length > 0 ? src.charAt(0) : 0;

			public char next() {
				char c = ch;
				if (index < length) {
					ch = src.charAt(index++);
				} else {
					ch = 0;
					index++;
				}
				return c;
			}

			public boolean hasNext() {
				return index <= length;
			}

			public boolean isJavaStart() {
				return Character.isJavaIdentifierStart(ch);
			}

			public boolean isJavaPart() {
				return Character.isJavaIdentifierPart(ch);
			}

			public boolean isWhiteSpace() {
				return Character.isWhitespace(ch);
			}
			
		};
	}
	
	/**
	 * @return current token returned from source
	 * @throws IndexOutOfBoundsException in case next() is never called on this instance
	 * or source of characters is empty.
	 */
	public String current() {
		if (current == null) {
			throw new IndexOutOfBoundsException("next() is never called or source is empty");
		}
		return current;
	}
	
	/**
	 * @return next token returned from source
	 * @throws IndexOutOfBoundsException in case hasNext() returns <code>false</code>.
	 */
	public String next() {
		if (!hasNext()) {
			throw new IndexOutOfBoundsException("Unable to continue - source of characters is empty");
		}
		StringBuilder result = new StringBuilder();
		
		if (iterator.isJavaStart()) {
			result.append(iterator.next());
			while(iterator.hasNext() && iterator.isJavaPart()) {
				result.append(iterator.next());
			}
		} else {
			result.append(iterator.next());
		}
		while (iterator.isWhiteSpace() && iterator.hasNext()) {
			iterator.next();
		}
		
		
		return current = result.toString();
	}
	
	/**
	 * @return <code>true</code> if there are some tokens awailable in the source and false otherwise.
	 */
	public boolean hasNext() {
		return iterator.hasNext();
	}
}
